﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using SimpleOrmCFPC.Apis.Db;
using SimpleOrmCFPC.Core.Model;
using SimpleOrmCFPC.Core.Query.Select;
using SimpleOrmCFPC.Core.Query.Where;
using SimpleOrmCFPC.Helpers;

namespace SimpleOrmCFPC.Core.Query.Requests
{
    public static class CompileResultExtensions
    {
        public static IWhereElement Join(this IEnumerable<IWhereElement> results)
        {
            return new OperatorWhereElement("AND", results);
        }
    }

    public class RequestExecutor
    {
        private IDb _executor;

        public RequestExecutor(IDb executor)
        {
            _executor = executor;
        }

        public IEnumerable<T> Get<T>(SQLRequest request)
        {
            if (request._selects.Count == 0)
                request._selects.AddRange(SelectComputer.ExtractSelects<T>());
            if (typeof (T).IsEntity())
            {
                MakeJoin(typeof (T), request, "");
            }
            var whereToSqlBuilder = new WhereToSqlBuilder();
            var whereElement = request._where.Join();
            var values = new List<object>();
            var whereBuilder = new StringBuilder();
            whereElement.Visit(whereToSqlBuilder, whereBuilder, values);
            string request1 = CreateRequest(request, whereBuilder.ToString());

            ISQLReader reader = _executor.Execute(request1, values);
            if (typeof(T).IsEntity())
            {
                var ts1 = new List<T>();

                while (reader.Next())
                {
                    var t = Read(typeof(T), reader, "");
                    ts1.Add((T)t);
                }
                return ts1;
            }
            var ts = new List<T>();

            while (reader.Next())
            {
                ts.Add((T)reader.Read(request._selects[0], typeof(T)));
            }
            return ts;
            
        }

        private void MakeJoin(Type type, SQLRequest request, string fieldManyToOne)
        {
            if (fieldManyToOne != string.Empty)
            {
                request.tableNames.Add(type.GetEntityName());
                string fieldKey = type.Name + "." + type.GetPrimaryKey().GetColumnName();
                request._where.Add(new SimpleWhereElement(string.Format("{0} = {1}", fieldManyToOne, fieldKey), new List<object>()));
            }
                
            foreach (PropertyInfo property in type.GetProperties())
            {
                if (property.HasAttribute<ManyToOneAttribute>())
                {
                    string fieldKey = type.Name + "." + property.Name + "Id";
                    MakeJoin(property.PropertyType, request, fieldKey);
                }
            }
        }

        private static object Read(Type type, ISQLReader reader, string prefix)
        {
            var t = Activator.CreateInstance(type);
            foreach (PropertyInfo property in type.GetProperties())
            {
                if (property.HasAttribute<ManyToOneAttribute>())
                {
                    property.SetValue(t, Read(property.PropertyType, reader, prefix + property.Name), new object[0]);
                }
                else
                {
                    property.SetValue(t, reader.Read(prefix + property.Name, property.PropertyType), new object[0]);
                }
            }
            return t;
        }

        public string CreateRequest(SQLRequest request, string whereToSqlBuilder)
        {
            var selects = request._selects;
            if (selects.Count == 0)
                selects = new List<string>() {"*"};
            string cmdText = string.Format("select {0} from ", string.Join(",", selects.ToArray())) + string.Join(",", request.tableNames.ToArray());
            if (request._where.Count != 0)
            {
                cmdText += " where " + whereToSqlBuilder;
            }
            if (request._orderBys.Count > 0)
            {
                string t = string.Join(", ",
                                       request._orderBys.Select(o => "\"" + o.ColumnName + "\"" + (o.Ascending ? "" : " desc")).
                                           ToArray());
                cmdText += " order by " + t;
            }
            if (request._limit.HasValue)
            {
                cmdText += " limit " + request._limit.Value;
            }
            if (request._offset.HasValue && request._limit.HasValue)
            {
                cmdText += " offset " + request._offset.Value;
            }
            return cmdText;
        }
    }
}
